﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace EECS395Library
{
    public static class Extensions
    {
        /// <summary>
        /// Calculates the size of a Texture2D
        /// </summary>
        public static Vector2 Size(this Texture2D texture)
        {
            return new Vector2(texture.Width, texture.Height);
        }

        /// <summary>
        /// Applies an action to an object and returns its instance. Intended to be used as a more general form of object initializers.
        /// </summary>
        public static T With<T>(this T obj, Action<T> action)
        {
            if (obj != null)
                action(obj);
            return obj;
        }

        /// <summary>
        /// Swaps the items at indices i and j in an array
        /// </summary>
        public static void Swap<T>(this T[] array, int i, int j)
        {
            T tmp = array[j];
            array[j] = array[i];
            array[i] = tmp;
        }

        /// <summary>
        /// Swaps the items at indices i and j in a list
        /// </summary>
        public static void Swap<T>(this List<T> list, int i, int j)
        {
            T tmp = list[j];
            list[j] = list[i];
            list[i] = tmp;
        }

#if DEBUG
        private static readonly Random Random = new Random(0);
#endif
#if RELEASE
        private static readonly Random Random = new Random();
#endif

        /// <summary>
        /// Returns a random element from the list.
        /// </summary>
        public static T RandomElement<T>(this List<T> list)
        {
            return list[Random.Next(0, list.Count)];
        }

        /// <summary>
        /// Returns a random element from the array.
        /// </summary>
        public static T RandomElement<T>(this T[] array)
        {
            return array[Random.Next(0, array.Length)];
        }

        /// <summary>
        /// Returns a random element from the enumerable.
        /// </summary>
        public static T RandomElement<T>(this IEnumerable<T> enumerable)
        {
            return enumerable.ElementAt(Random.Next(0, enumerable.Count()));
        }

        /// <summary>
        /// Standard implementation of an in-place fisher-yates shuffle. O(n) time
        /// </summary>
        public static void Shuffle<T>(this T[] array)
        {
            for (int i = array.Length; i > 1; i--)
            {
                // Pick random element to swap.
                int j = Random.Next(i); // 0 <= j <= i-1
                // Swap.
                array.Swap(j, i - 1);
            }
        }

        /// <summary>
        /// Standard implementation of an in-place fisher-yates shuffle. O(n) time
        /// </summary>
        public static void Shuffle<T>(this List<T> list)
        {
            for (int i = list.Count; i > 1; i--)
            {
                // Pick random element to swap.
                int j = Random.Next(i); // 0 <= j <= i-1
                // Swap.
                list.Swap(j, i - 1);
            }
        }

        /// <summary>
        /// Creates a new array with the elements shuffled.
        /// </summary
        public static T[] ToShuffled<T>(this T[] array)
        {
            var newArray = array.ToArray();
            newArray.Shuffle();
            return newArray;
        }

        /// <summary>
        /// Creates a new array with the elements shuffled.
        /// </summary
        public static T[] ToShuffled<T>(this IEnumerable<T> items)
        {
            var newArray = items.ToArray();
            newArray.Shuffle();
            return newArray;
        }

        /// <summary>
        /// Creates a new set from the input enumerable. Duplicate elements are removed.
        /// </summary>
        public static ISet<T> ToSet<T>(this IEnumerable<T> input)
        {
            HashSet<T> items = new HashSet<T>();

            foreach (var item in input)
            {
                items.Add(item);
            }

            return items;
        }

        /// <summary>
        /// Returns an enumerable of a sequence with the duplicate elements removed.
        /// </summary>
        public static IEnumerable<T> Unique<T>(this IEnumerable<T> input)
        {
            return input.ToSet().ToArray();
        }

        /// <summary>
        /// Returns a normally distributed double with the specified mean and standard deviation
        /// </summary>
        public static double NextNormal(this Random random, double mean = 0, double stdev = 1.0)
        {
            double u = random.NextDouble();
            double v = random.NextDouble();
            double g = Math.Sqrt(-2 * Math.Log(u)) * Math.Cos(2 * Math.PI * v);
            return (mean + stdev * g);
        }

        /// <summary>
        /// Returns a normally distributed float with the specified mean and standard deviation
        /// </summary>
        public static float NextNormalF(this Random random, float mean = 0, float stdev = 1f)
        {
            return (float)random.NextNormal((double)mean, (double)stdev);
        }

        /// <summary>
        /// Returns an exponentially distributed double with the specified mean
        /// </summary>
        public static double NextExponential(this Random random, double mean = 1.0)
        {
            return mean * (-Math.Log(random.NextDouble()));
        }

        /// <summary>
        /// Returns an exponentially distributed float with the specified mean
        /// </summary>
        public static float NextExponentialF(this Random random, float mean = 1f)
        {
            return (float)random.NextExponential((double)mean);
        }

        /// <summary>
        /// Returns the next double within a specified range.
        /// </summary>
        public static double NextDouble(this Random random, double min, double max)
        {
            return random.NextDouble() * (max - min) + min;
        }

        /// <summary>
        /// Returns the next float within a specified range.
        /// </summary>
        public static float NextFloat(this Random random, float min = 0f, float max = 1f)
        {
            return (float)(random.NextDouble() * (max - min) + min);
        }

        /// <summary>
        /// Returns true with probability <paramref name="probability"/>
        /// </summary>
        public static bool Chance(this Random random, double probability)
        {
            Debug.Assert(probability <= 1.0 && probability >= 0.0);
            return Random.NextDouble() <= probability;
        }

        /// <summary>
        /// Applies <paramref name="action"/> to each item of <paramref name="enumeration"/>
        /// </summary>
        public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
        {
            foreach (T item in enumeration)
            {
                action(item);
            }
        }
    }
}